package logger

import (
	"gitee.com/lipore/plume/logger"
	"github.com/gin-gonic/gin"
	"net/http"
	"regexp"
	"time"
)

// Config defines the config for logger middleware
type options struct {
	logger logger.Logger
	// UTC a boolean stating whether to use UTC time zone or local.
	utc            bool
	skipPath       []string
	skipPathRegexp *regexp.Regexp
	// the log level used for request with status code < 400
	defaultLevel logger.Level
	// the log level used for request with status code between 400 and 499
	clientErrorLevel logger.Level
	// the log level used for request with status code >= 500
	serverErrorLevel logger.Level
}

type Option interface {
	apply(o *options)
}

type funcOption struct {
	fn func(o *options)
}

func (f *funcOption) apply(o *options) {
	f.fn(o)
}

func newFuncOption(fn func(o *options)) *funcOption {
	return &funcOption{
		fn: fn,
	}
}

func WithLogger(l logger.Logger) Option {
	return newFuncOption(func(o *options) {
		o.logger = l
	})
}

func WithUtc(utc bool) Option {
	return newFuncOption(func(o *options) {
		o.utc = utc
	})
}
func WithSkipPath(skipPath []string) Option {
	return newFuncOption(func(o *options) {
		o.skipPath = skipPath
	})
}
func WithSkipPathRegexp(skipPathRegexp *regexp.Regexp) Option {
	return newFuncOption(func(o *options) {
		o.skipPathRegexp = skipPathRegexp
	})
}
func WithDefaultLevel(defaultLevel logger.Level) Option {
	return newFuncOption(func(o *options) {
		o.defaultLevel = defaultLevel
	})
}
func WithClientErrorLevel(clientErrorLevel logger.Level) Option {
	return newFuncOption(func(o *options) {
		o.clientErrorLevel = clientErrorLevel
	})
}
func WithServerErrorLevel(serverErrorLevel logger.Level) Option {
	return newFuncOption(func(o *options) {
		o.serverErrorLevel = serverErrorLevel
	})
}

func SetLogger(opts ...Option) gin.HandlerFunc {
	o := &options{
		defaultLevel:     logger.INFO,
		clientErrorLevel: logger.WARN,
		serverErrorLevel: logger.WARN,
		logger:           logger.GetLogger(),
	}

	for _, opt := range opts {
		opt.apply(o)
	}

	if o.logger == nil {
		panic("logger is not set")
	}

	var skip map[string]struct{}
	if length := len(o.skipPath); length > 0 {
		skip = make(map[string]struct{}, length)
		for _, path := range o.skipPath {
			skip[path] = struct{}{}
		}
	}

	return func(c *gin.Context) {
		start := time.Now()
		path := c.Request.URL.Path
		raw := c.Request.URL.RawQuery
		if raw != "" {
			path = path + "?" + raw
		}

		c.Next()
		track := true

		if _, ok := skip[path]; ok {
			track = false
		}
		if track && o.skipPathRegexp != nil && o.skipPathRegexp.MatchString(path) {
			track = false
		}

		if track {
			end := time.Now()
			if o.utc {
				start = start.UTC()
				end = end.UTC()
			}
			latency := end.Sub(start)

			l := o.logger.WithFields(map[string]interface{}{
				"status":     c.Writer.Status(),
				"method":     c.Request.Method,
				"path":       c.Request.URL.Path,
				"ip":         c.ClientIP(),
				"latency":    latency,
				"user_agent": c.Request.UserAgent(),
			})

			msg := "Request"
			if len(c.Errors) > 0 {
				msg = c.Errors.String()
			}

			switch {
			case c.Writer.Status() >= http.StatusBadRequest && c.Writer.Status() < http.StatusInternalServerError:
				{
					l.V(o.clientErrorLevel).Print(msg)
				}
			case c.Writer.Status() >= http.StatusInternalServerError:
				{
					l.V(o.serverErrorLevel).Print(msg)
				}
			default:
				l.V(o.defaultLevel).Print(msg)
			}
		}
	}
}
